package com.csaba.connector.otp.model;

import java.io.FileOutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;

import com.csaba.connector.ServiceException;
import com.csaba.connector.model.Session;
import com.csaba.util.LoggerHelper;

public class OTPSession extends Session
{
	private final static Logger logger = LoggerHelper.getDefaultLogger();

	private final static String JSESSIONID = ";jsessionid=";
	public final static boolean DEBUG_FILE = true; //FIXME set to false

	private HttpClient client;

	private int operationCounter = 1;

	public OTPSession()
	{
		super();
		setBank(OTPBank.getInstance());
	}

	public int getOperationCounter()
	{
		return operationCounter++;
	}

	public String doPost( final String url, final NameValuePair[] params ) throws ServiceException
	{
		final String modifiedUrl = appendSessionId(url);
		final PostMethod method = new PostMethod(modifiedUrl);
		method.addParameters(params);
		logger.fine("POST: " + modifiedUrl);
		return execute(method);
	}

	public String doGet( final String url ) throws ServiceException
	{
		final String modifiedUrl = appendSessionId(url);
		final GetMethod method = new GetMethod(modifiedUrl);
		logger.fine("GET: " + modifiedUrl);
		return execute(method);
	}

	/**
	 * Appends session ID to the URL, if session ID is already set, and the URL is not yet containing session ID.
	 * @param url URL to check and be appended.
	 * @return the URL with the session ID set.
	 */
	private String appendSessionId( final String url )
	{
		if ( url.contains(JSESSIONID) || getSessionId() == null )
		{
			return url;
		}

		final StringBuilder result = new StringBuilder();
		final int queryPos = url.indexOf('?');
		if ( queryPos > -1 )
		{
			//query sting is available
			result.append(url.substring(0, queryPos));
			result.append(JSESSIONID);
			result.append(getSessionId());
			result.append(';');
			result.append(url.substring(queryPos));
		}
		else
		{//noquery
			result.append(url);
			result.append(JSESSIONID);
			result.append(getSessionId());
			result.append(';');
		}
		final String stringResult = result.toString();
		logger.fine("URL with session ID: " + stringResult);
		return stringResult;
	}

	private String execute( final HttpMethod method ) throws ServiceException
	{
		// lazy initialization
		if ( client == null )
		{
			client = new HttpClient();
		}

		//Provide custom retry handler is necessary
		method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false));

		try
		{
			final int statusCode = client.executeMethod(method);

			if ( statusCode == HttpStatus.SC_OK )
			{
				return method.getResponseBodyAsString();
			}
			else if ( statusCode == HttpStatus.SC_MOVED_TEMPORARILY || statusCode == HttpStatus.SC_MOVED_PERMANENTLY )
			{
				final Header locationHeader = method.getResponseHeader("location");
				if ( locationHeader != null )
				{
					final String redirectLocation = locationHeader.getValue();
					logger.fine("Being redirected to " + redirectLocation);
					detectSessionId(redirectLocation);
					return doGet(redirectLocation);

				}
				else
				{
					throw new ServiceException("OTP: Invalid redirect.", "Missing location.");
				}
			}
			else
			{
				throw new ServiceException("OTP: Unexpected HTTP response:" + statusCode, method.getStatusLine()
						.toString());
			}

		}
		catch ( final Exception e )
		{
			throw new ServiceException("Unexpected exception in HTTP communication.", e);
		}
		finally
		{
			// Release the connection.
			method.releaseConnection();
		}
	}

	/**
	 * Otp is using URL rewriting for session tracking. This method detects and store session ID if it is in the the received URL.
	 * @param url URL containing session ID. Maybe...
	 */
	private void detectSessionId( final String url )
	{
		int pos = url.indexOf(JSESSIONID);
		if ( pos > 0 )
		{
			String sessionId = url.substring(pos + JSESSIONID.length());
			pos = sessionId.indexOf('?'); //start of the querystring 
			if ( pos >= 0 )
			{
				sessionId = sessionId.substring(0, pos);
			}
			if ( getSessionId() != null && !getSessionId().equals(sessionId) )
			{
				logger.severe("Session ID is changed from " + getSessionId() + " to " + sessionId);
			}
			else
			{
				setSessionId(sessionId);
			}
		}
	}

	public void debugFile( final String fileName, final String content )
	{
		logger.fine(fileName + " result is received. Length: " + content.length());
		if ( OTPSession.DEBUG_FILE )
		{
			try
			{
				final FileOutputStream fos = new FileOutputStream("../otp.source/" + fileName + ".html");
				fos.write(content.getBytes("UTF8"));
				fos.flush();
				fos.close();
			}
			catch ( final Exception ex )
			{
				logger.log(Level.SEVERE, "Error creating debug file.", ex);
			}
		}

	}

	public static void main( final String[] args ) throws Exception
	{
		LoggerHelper.initLogger(Level.FINE);

		final OTPSession session = new OTPSession();
		final String result = session.doGet(OTPRequestFactory.PORTAL_URL_BASE);
		logger.fine(result);

		final FileOutputStream fos = new FileOutputStream("../otp.source/portalbase.html");
		fos.write(result.getBytes("UTF8"));
		fos.flush();
		fos.close();
	}
}
